/*
 *  Copyright 2009 Utkin Dmitry
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
 
/* 
 *  This file is part of the WSF Staff project.
 *  Please, visit http://code.google.com/p/staff for more information.
 */

/* 
**  mod_axis2services.c -- Apache sample axis2services module
**  [Autogenerated via ``apxs -n axis2services -g'']
**
**  To play with this sample module first compile it into a
**  DSO file and install it into Apache's modules directory 
**  by running:
**
**    $ apxs -c -i mod_axis2services.c
**
**  Then activate it in Apache's httpd.conf file for instance
**  for the URL /axis2services in as follows:
**
**    #   httpd.conf
**    LoadModule axis2services_module modules/mod_axis2services.so
**    <Location /axis2services>
**    SetHandler axis2services
**    </Location>
**
**  Then after restarting Apache via
**
**    $ apachectl restart
**
**  you immediately can request the URL /axis2services and watch for the
**  output of this module. This can be achieved for instance via:
**
**    $ lynx -mime_header http://localhost/axis2services 
**
**  The output should be similar to the following one:
**
**    HTTP/1.1 200 OK
**    Date: Tue, 31 Mar 1998 14:42:22 GMT
**    Server: Apache/1.3.4 (Unix)
**    Connection: close
**    Content-Type: text/html
**  
**    The sample page from mod_axis2services.c
*/ 

#include <unistd.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "ap_config.h"

#ifdef _DEBUG
FILE* pLog = NULL;
#define LABEL fprintf(pLog, "%s[%d]: %s\n", __FILE__, __LINE__, __FUNCTION__); fflush(pLog);

#define LOGRAW(str) fprintf(pLog, str); fflush(pLog);
#define LOG(str) fprintf(pLog, "%s[%d]: %s: %s\n", __FILE__, __LINE__, __FUNCTION__, str); fflush(pLog);
#define LOG1(str, arg1) fprintf(pLog, "%s[%d]: %s:" str "\n", __FILE__, __LINE__, __FUNCTION__, arg1); fflush(pLog);
#define LOG2(str, arg1, arg2) fprintf(pLog, "%s[%d]: %s:" str "\n", __FILE__, __LINE__, __FUNCTION__, arg1, arg2); fflush(pLog);
#define LOG3(str, arg1, arg2, arg3) fprintf(pLog, "%s[%d]: %s:" str "\n", __FILE__, __LINE__, __FUNCTION__, arg1, arg2, arg3); fflush(pLog);

void dump(const char* szData, unsigned long ulSize)
{
  const char* pBuff = szData;
  unsigned long i = 0;
  unsigned long j = 0;

  for(i = 0; i < ulSize; i += 0x10) 
  {
    if((i % 0x10) == 0) 
      fprintf(pLog, "\n0x%.8x   ", i + szData);

    for(j = 0; ((i + j) < ulSize) && (j < 0x10); j++) 
      fprintf(pLog, "%.2x ", (unsigned char)pBuff[i+j]);

    for(; j < 0x10; j++)
      fprintf(pLog, "   ");

    fprintf(pLog, "  ");

    for(j = 0; ((i + j) < ulSize) && (j < 0x10); j++) 
      fprintf(pLog, "%c", (unsigned char)pBuff[i+j] > ' ' ? pBuff[i+j] : '.');
  }
  fprintf(pLog, "\n\n");
  fflush(pLog);
}
#else
#define LABEL
#define LOGRAW(str)
#define LOG(str)
#define LOG1(str, arg1)
#define LOG2(str, arg1, arg2)
#define LOG3(str, arg1, arg2, arg3)
#define dump(a,b)
#endif

void CloseSocket(int nSockID)
{
  char buf[32];
  struct timeval tv;

  if (nSockID < 0)
    return;

  shutdown(nSockID, SHUT_WR);

  tv.tv_sec = 0;
  tv.tv_usec = 1;
  setsockopt(nSockID, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));

  recv(nSockID, buf, 32, 0);
  close(nSockID);
}

int CreateSocket()
{
  int nRet = 0;
  int nSockID = 0;
  struct sockaddr_in saServer;
  struct timeval tv;

  nSockID = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  if(nSockID == -1)
    return -2;

  saServer.sin_family = AF_INET;
  saServer.sin_port = htons(9090);
  saServer.sin_addr.s_addr = inet_addr("127.0.0.1");

  nRet = connect(nSockID, &saServer, sizeof(saServer));
  if(nRet == -1)
  {
    CloseSocket(nSockID);
    return -3;
  }

  {
    int nNoDelay = 1;
    setsockopt(nSockID, IPPROTO_TCP, TCP_NODELAY, (const char*)&nNoDelay, sizeof(nNoDelay));
  }

  {
    struct linger stLinger;
    stLinger.l_onoff = 1;
    stLinger.l_linger = 5;
    setsockopt(nSockID, SOL_SOCKET, SO_LINGER, (const char*)&stLinger, sizeof(struct linger));
  }

  tv.tv_sec = 60;
  tv.tv_usec = 0;
  setsockopt(nSockID, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));
  
  return nSockID;
}

static int util_read(request_rec* pReq, const char** rbuf, int* pnLength)
{
  int nResult = OK;

  if ((nResult = ap_setup_client_block(pReq, REQUEST_CHUNKED_ERROR)) != OK)
    return nResult;

  if (ap_should_client_block(pReq))
  {
    char argsbuffer[HUGE_STRING_LEN];
    int rsize, len_read, rpos=0;
    long nLength = pReq->remaining;
    *pnLength = nLength;
  
    *rbuf = apr_pcalloc(pReq->pool, nLength + 1);
//    ap_hard_timeout("util_read", pReq);

    while ((len_read = ap_get_client_block(pReq, argsbuffer, sizeof(argsbuffer))) > 0) 
    {
//      ap_reset_timeout(pReq);

      if((rpos + len_read) > nLength) 
        rsize = nLength - rpos;
      else
        rsize = len_read;

      memcpy((char*)*rbuf + rpos, argsbuffer, rsize);
      rpos += rsize;
    }
//    ap_kill_timeout(pReq);
  }

  return nResult;
}

int InvokeRequest(request_rec* pReq, const char* szRequest, int nRequestSize, const char** pszAnswer, int *pnAnswerSize)
{
  char szHttpHeader[512];
  int nRet = 0;
  int nSockID = 0;
  int nHttpHeaderLength = 0;
  int nBufferSize = 0;

LABEL  
  if (pReq == NULL || pszAnswer == NULL || pnAnswerSize == NULL)
    return 1;
    
  *pszAnswer = NULL;
  *pnAnswerSize = 0;

  nSockID = CreateSocket();
  if(nSockID < 0)
    return nSockID;

LABEL  
  if (pReq->method_number == M_POST)
  {
    const char* szSoapAction = "\"\"";
    const char* szAprSoapAction = apr_table_get(pReq->headers_in, "SOAPAction");

    if (szAprSoapAction)
    {
      szSoapAction = szAprSoapAction;
    }

    nHttpHeaderLength = snprintf(szHttpHeader, sizeof(szHttpHeader),
        "POST %s HTTP/1.1\r\n"
        "User-Agent: staff http module 1.0\r\n"
        "SOAPAction: %s\r\n"
        "Content-Length: %d\r\n"
        "Content-Type: text/xml;charset=UTF-8\r\n"
        "Host: 127.0.0.1:9090\r\n\r\n",
        pReq->unparsed_uri,
        szSoapAction,
        nRequestSize);
  }
  else
  {
    nHttpHeaderLength = snprintf(szHttpHeader, sizeof(szHttpHeader),
        "GET %s HTTP/1.1\r\n"
        "User-Agent: staff http module 1.0\r\n"
        "Content-Length: %d\r\n"
        "Content-Type: text\r\n"
        "Host: 127.0.0.1:9090\r\n\r\n",
        pReq->unparsed_uri,
        nRequestSize);
  }
  
LABEL
  nRet = send(nSockID, szHttpHeader, nHttpHeaderLength, 0);
  if(nRet == -1)
  {
    CloseSocket(nSockID);
    return 4;
  }
  
LABEL
  if (szRequest)
  {
    nRet = send(nSockID, szRequest, nRequestSize, 0);
    if(nRet == -1)
    {
      CloseSocket(nSockID);
      return 4;
    }
  }
  
LABEL
  //////////////////////////////////////////////////
  // retrive and parse HttpHeader
  {
    int bHeaderParsed = 0;
    int nReceived = 0;

    char szLineEnd[4] = "";
    int nLineEndLength = 0;

    char* szCurr = szHttpHeader; // current address for data block receiving

    const char* szBegin = szCurr;
    const char* szEnd = NULL;


    while (!bHeaderParsed)
    {
LOG1("wanna to receive %d bytes", (sizeof(szHttpHeader) - nReceived - 1));
      nRet = recv(nSockID, szCurr, sizeof(szHttpHeader) - nReceived - 1, 0);
LOG1("received %d bytes:", nRet);
      if(nRet <= 0)
      {
LOG1("error receiving: %s", strerror(errno));
        CloseSocket(nSockID);
        return 5;
      }
dump(szCurr, nRet);

      nReceived += nRet;
      

      szCurr[nRet] = '\0';
      
      szEnd = strpbrk(szBegin, "\n\r");

      // detect line end of header
      if (nLineEndLength == 0 && szEnd != NULL)
      {
        szLineEnd[0] = *szEnd;
        if (*(szEnd + 1) == '\n' || *(szEnd + 1) == '\r')
        {
          szLineEnd[1] = *(szEnd + 1);
          szLineEnd[2] = 0;
          nLineEndLength = 2;
        } else
        {
          szLineEnd[1] = 0;
          nLineEndLength = 1;
        }
      }

LABEL  
      // parse header
      while (szEnd != NULL)
      {
LABEL  
        if (szEnd == szBegin && strncmp(szBegin + nLineEndLength, szLineEnd, nLineEndLength)) // there is body part
        {
          if(*pszAnswer == NULL) // body part without header!!
          {
LABEL  
            CloseSocket(nSockID);
            return 6;
          }
          
          szEnd += nLineEndLength;
          
LABEL  
LOG1("(szEnd - szHttpHeader): %d", (szEnd - szHttpHeader));
LABEL  
LOG3("buffer ptr: %p, szEnd: %p, nReceived: %d", *pszAnswer, szEnd, nReceived);
          nReceived -= (szEnd - szHttpHeader);

          if(nReceived > 0)
          {
LABEL  
            memcpy((char*)(*pszAnswer), szEnd, nReceived);
          }
LABEL  

          szCurr = (char*)(*pszAnswer) + nReceived;
          bHeaderParsed = 1;
          break;
        }

LABEL  
        if(strncmp(szBegin, "Content-Length:", 15) == 0)
        {
LABEL  
          *pnAnswerSize = atoi(szBegin + 15);
          *pszAnswer = apr_pcalloc(pReq->pool, (*pnAnswerSize) + 1);
          ((char*)(*pszAnswer))[*pnAnswerSize] = '\0';
LOG1("data size: %d", *pnAnswerSize);
LOG1("buffer ptr: %p", *pszAnswer);
        }
        else
        if(strncmp(szBegin, "Content-Type:", 13) == 0)
        {
          char* szContentTypeCopy = NULL;
          const char* szContentType = szBegin + 13;
          const char* szContentTypeEnd = NULL;
          while (*szContentType == ' ' || *szContentType == '\t')
          {
            ++szContentType;
          }
LABEL  
          szContentTypeEnd = strpbrk(szContentType, "\n\r");
          if (!szContentTypeEnd)
          {
            return 8;
          }

          szContentTypeCopy = apr_pcalloc(pReq->pool, szContentTypeEnd - szContentType + 1);
          strncpy(szContentTypeCopy, szContentType, szContentTypeEnd - szContentType);
          szContentTypeCopy[szContentTypeEnd - szContentType] = '\0';

          pReq->content_type = szContentTypeCopy;
        }

        szEnd += nLineEndLength;
        szBegin = szEnd;
        szEnd = strstr(szBegin, szLineEnd);
      }
      
LABEL  
      if(!bHeaderParsed)
        szCurr += nRet;
    }
LABEL  


    ///////////////////////////////////////////////////////////
    // body message
    while(nReceived < *pnAnswerSize)
    {
      nRet = recv(nSockID, szCurr, *pnAnswerSize - nReceived, 0);
      if(nRet == -1)
      {
        CloseSocket(nSockID);
        return 7;
      }

      nReceived += nRet;
      szCurr += nRet;
    }
  }

  CloseSocket(nSockID);

LABEL  
  return 0;
}

/* content handler */
static int axis2services_handler(request_rec* pReq)
{
  int nRet = OK;
  if (strcmp(pReq->handler, "axis2services"))
    return DECLINED;
  
  if (pReq->header_only)
    return nRet;

LABEL  

  if (pReq->method_number == M_POST || pReq->method_number == M_GET)
  {
    const char* szRequest = NULL;
    int nRequestSize = 0;

    const char* szAnswer = NULL;
    int nAnswerSize = 0;
  
    if ((nRet = util_read(pReq, &szRequest, &nRequestSize)) != OK)
    {
      return nRet;
    }
    
    nRet = InvokeRequest(pReq, szRequest, nRequestSize, &szAnswer, &nAnswerSize);

    if (nRet != 0)
    {
      char szError[256];

      pReq->content_type = "text";
      if(szAnswer != NULL)
      {
        ap_rputs(szAnswer, pReq);
        ap_rputs("\n\n\n", pReq);
      }

      snprintf(szError, sizeof(szError), "error while InvokeRequest #%d\n", nRet);
      ap_rputs(szError, pReq);
      
//      return 500;
      return OK;
    }

LOG2(" \n\nRESULT(size=%d): %s\n\n", nAnswerSize, szAnswer);
dump(szAnswer, nAnswerSize);
//dump(szAnswer, nAnswerSize > 4000 ? 4000 : nAnswerSize);
//    pReq->content_type = "text/xml;charset=UTF-8";
    ap_rwrite(szAnswer, nAnswerSize, pReq);
  }
  else
  {
    return nRet;
  }

//LOG1("%s", "--------------------------------------------------------");      
  return OK;
}

static void axis2services_register_hooks(apr_pool_t *p)
{
  ap_hook_handler(axis2services_handler, NULL, NULL, APR_HOOK_MIDDLE);
#ifdef _DEBUG
pLog = fopen("/tmp/apm.log", "wt");
LABEL
#endif
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA axis2services_module = {
    STANDARD20_MODULE_STUFF, 
    NULL,                  /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    NULL,                  /* table of config file commands       */
    axis2services_register_hooks  /* register hooks                      */
};

